sapling/eden/integration/helpers/fake_edenfs.cpp
Chad Austin 4f360eafd2 clang-format
Summary: Formatting had diverged in a few places. Fix that up.

Reviewed By: fanzeyi

Differential Revision: D18123219

fbshipit-source-id: 832cdd70789642f665a029196998928a9173be81
2019-10-24 14:56:43 -07:00

403 lines
11 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#include <folly/init/Init.h>
#include <folly/io/async/AsyncSignalHandler.h>
#include <folly/logging/Init.h>
#include <folly/logging/xlog.h>
#include <gflags/gflags.h>
#include <signal.h>
#include <sysexits.h>
#include <thrift/lib/cpp2/server/ThriftServer.h>
#include <algorithm>
#include <array>
#include <chrono>
#include <fstream>
#include <iterator>
#include <optional>
#include <string>
#include <thread>
#include <vector>
#include "eden/fs/eden-config.h"
#include "eden/fs/fuse/privhelper/UserInfo.h"
#include "eden/fs/inodes/EdenMount.h"
#include "eden/fs/service/EdenInit.h"
#include "eden/fs/service/StartupLogger.h"
#include "eden/fs/service/Systemd.h"
#include "eden/fs/service/gen-cpp2/StreamingEdenService.h"
#include "eden/fs/utils/PathFuncs.h"
using namespace std::literals::chrono_literals;
using apache::thrift::ThriftServer;
using facebook::fb303::cpp2::fb303_status;
using folly::EventBase;
using folly::StringPiece;
using std::make_shared;
using std::string;
DEFINE_bool(
edenfs,
false,
"This argument must be supplied to confirm you intend to run "
"edenfs instead of eden");
DEFINE_bool(allowRoot, false, "Allow running eden directly as root");
DEFINE_string(
cleanShutdownFile,
"",
"If set, create the given file when shutting down cleanly");
DEFINE_bool(
allowExtraArgs,
false,
"Do not error out if extra command line options were specified");
DEFINE_bool(
exitWithoutCleanupOnStop,
false,
"Respond to stop requests by exiting abruptly");
DEFINE_bool(
failDuringStartup,
false,
"Instead of reporting success after starting up, report failure and exit");
DEFINE_bool(ignoreStop, false, "Ignore attempts to stop edenfs");
DEFINE_double(
sleepBeforeGetPid,
0.0,
"Sleep for this many seconds before responding to getPid");
DEFINE_double(
sleepBeforeStop,
0.0,
"Sleep for this many seconds before stopping");
using namespace facebook::eden;
FOLLY_INIT_LOGGING_CONFIG(".=INFO,eden=DBG2");
namespace {
class FakeEdenServiceHandler;
template <class Rep, class Ratio>
std::string prettyPrint(
std::chrono::duration<Rep, Ratio> duration,
bool addSpace = true);
enum class StopBehavior {
DoNothing,
ExitWithoutCleanup,
TerminateEventLoop,
};
class FakeEdenServer {
public:
FakeEdenServer() {}
void run(folly::SocketAddress thriftAddress, StartupLogger& startupLogger);
void stop(StringPiece reason) {
XLOG(INFO) << "received stop request: " << reason;
if (stopSleepDuration_ > 0ms) {
XLOG(INFO) << "pausing stop attempt for "
<< prettyPrint(stopSleepDuration_);
std::this_thread::sleep_for(stopSleepDuration_);
}
switch (stopBehavior_) {
case StopBehavior::DoNothing:
XLOG(INFO) << "ignoring stop attempt";
break;
case StopBehavior::ExitWithoutCleanup:
XLOG(INFO) << "exiting without cleanup";
_exit(1);
case StopBehavior::TerminateEventLoop:
XLOG(INFO) << "stopping";
eventBase_->terminateLoopSoon();
break;
}
}
uint64_t getPid() const {
if (getPidSleepDuration_ > 0ms) {
XLOG(INFO) << "pausing getPid call for "
<< prettyPrint(getPidSleepDuration_);
std::this_thread::sleep_for(getPidSleepDuration_);
}
return getpid();
}
void setStopBehavior(StopBehavior stopBehavior) {
stopBehavior_ = stopBehavior;
}
void setGetPidSleepDuration(std::chrono::milliseconds getPidSleepDuration) {
getPidSleepDuration_ = getPidSleepDuration;
}
void setStopSleepDuration(std::chrono::milliseconds stopSleepDuration) {
stopSleepDuration_ = stopSleepDuration;
}
void setCleanShutdownFile(AbsolutePath path) {
cleanShutdownFile_ = path;
}
void setCommandLine(std::vector<string> commandLine) {
// Note that setCommandLine() is not thread-safe.
// It should only be called immediately after construction of the
// FakeEdenServer, before it can be used from multiple threads.
commandLine_ = std::move(commandLine);
}
std::vector<string> getCommandLine() {
return commandLine_;
}
private:
void reportCleanShutdown() {
if (cleanShutdownFile_) {
folly::File{cleanShutdownFile_->c_str(), O_WRONLY | O_CREAT};
}
}
EventBase* eventBase_{nullptr};
ThriftServer server_;
std::shared_ptr<FakeEdenServiceHandler> handler_;
StopBehavior stopBehavior_{StopBehavior::TerminateEventLoop};
std::chrono::milliseconds getPidSleepDuration_{0ms};
std::chrono::milliseconds stopSleepDuration_{0ms};
std::optional<AbsolutePath> cleanShutdownFile_{};
std::vector<string> commandLine_;
};
class FakeEdenServiceHandler : virtual public StreamingEdenServiceSvIf {
public:
explicit FakeEdenServiceHandler(FakeEdenServer* server) : server_{server} {}
fb303_status getStatus() override {
return status_;
}
void setOption(std::unique_ptr<string> name, std::unique_ptr<string> value)
override {
auto badOption = [&]() {
auto errMsg = folly::to<string>(
"invalid value for ", *name, " setting: \"", *value, "\"");
XLOG(ERR) << errMsg;
throw std::invalid_argument(errMsg);
};
if (*name == "honor_stop") {
auto boolValue = folly::tryTo<bool>(*value);
if (boolValue.hasError()) {
badOption();
}
server_->setStopBehavior(
boolValue.value() ? StopBehavior::TerminateEventLoop
: StopBehavior::DoNothing);
} else if (*name == "status") {
if (*value == "starting") {
status_ = fb303_status::STARTING;
} else if (*value == "alive") {
status_ = fb303_status::ALIVE;
} else if (*value == "stopping") {
status_ = fb303_status::STOPPING;
} else {
badOption();
}
}
}
void getDaemonInfo(DaemonInfo& result) override {
result.pid = server_->getPid();
result.commandLine = server_->getCommandLine();
}
void listMounts(std::vector<MountInfo>& /* results */) override {}
void initiateShutdown(std::unique_ptr<string> reason) override {
server_->stop(folly::to<string>(
"received initiateShutdown() thrift requested: ", reason->c_str()));
}
private:
FakeEdenServer* server_{nullptr};
fb303_status status_{fb303_status::ALIVE};
};
class SignalHandler : public folly::AsyncSignalHandler {
public:
SignalHandler(EventBase* eventBase, FakeEdenServer* server)
: AsyncSignalHandler(eventBase), server_{server} {
registerSignalHandler(SIGINT);
registerSignalHandler(SIGTERM);
}
void signalReceived(int sig) noexcept override {
switch (sig) {
case SIGINT:
server_->stop("received SIGINT");
break;
case SIGTERM:
server_->stop("received SIGTERM");
break;
default:
XLOG(INFO) << "received signal " << sig;
break;
}
}
private:
FakeEdenServer* server_{nullptr};
};
void FakeEdenServer::run(
folly::SocketAddress thriftAddress,
StartupLogger& startupLogger) {
eventBase_ = folly::EventBaseManager::get()->getEventBase();
// Create the ThriftServer object
auto handler = make_shared<FakeEdenServiceHandler>(this);
server_.setInterface(handler);
server_.setAddress(thriftAddress);
// Set up a signal handler to ignore SIGINT and SIGTERM
// This lets our integration tests exercise the case where edenfs does not
// shut down on its own.
SignalHandler signalHandler(eventBase_, this);
// Run the thrift server
server_.setup();
if (FLAGS_failDuringStartup) {
startupLogger.exitUnsuccessfully(
1,
"Started successfully, but reporting failure because --failDuringStartup was specified");
}
startupLogger.success();
eventBase_->loopForever();
reportCleanShutdown();
}
bool acquireLock(AbsolutePathPiece edenDir) {
const auto lockPath = edenDir + "lock"_pc;
auto lockFile = folly::File(lockPath.value(), O_WRONLY | O_CREAT);
if (!lockFile.try_lock()) {
return false;
}
// Write the PID (with a newline) to the lockfile.
folly::ftruncateNoInt(lockFile.fd(), /* len */ 0);
const auto pidContents = folly::to<std::string>(getpid(), "\n");
folly::writeNoInt(lockFile.fd(), pidContents.data(), pidContents.size());
// Intentionally leak the lock FD so we hold onto it until we exit.
lockFile.release();
return true;
}
} // namespace
int main(int argc, char** argv) {
// Drop privileges
auto identity = UserInfo::lookup();
identity.dropPrivileges();
auto originalCommandArguments =
std::vector<std::string>{&argv[0], &argv[argc]};
auto init = folly::Init(&argc, &argv);
#if EDEN_HAVE_SYSTEMD
if (FLAGS_experimentalSystemd) {
XLOG(INFO) << "Running in experimental systemd mode";
}
#endif
// Reference FLAGS_edenfsctlPath so that it is linked into fake_edenfs
// when compiling in opt mode. Without this reference the linker will
// discard the symbol and our tests will claim that this flag is unknown
XLOG(INFO) << "edenfsctlPath is " << FLAGS_edenfsctlPath;
if (argc != 1 && !FLAGS_allowExtraArgs) {
fprintf(stderr, "error: unexpected trailing command line arguments\n");
return 1;
}
if (!FLAGS_edenfs) {
XLOG(ERR) << "the --edenfs flag is required";
return 1;
}
std::unique_ptr<EdenConfig> edenConfig;
try {
edenConfig = getEdenConfig(identity);
} catch (const ArgumentError& ex) {
fprintf(stderr, "%s\n", ex.what());
return EX_SOFTWARE;
}
auto edenDir = edenConfig->edenDir.getValue();
auto logPath = makeDefaultLogDirectory(edenDir) + getDefaultLogFileName();
auto startupLogger = daemonizeIfRequested(logPath.value());
// Acquire the lock file
if (!acquireLock(edenDir)) {
startupLogger->exitUnsuccessfully(1, "Failed to acquire lock file");
}
startupLogger->log("Starting fake edenfs daemon");
// Get the path to the thrift socket.
auto thriftSocketPath = edenDir + "socket"_pc;
folly::SocketAddress thriftAddress;
thriftAddress.setFromPath(thriftSocketPath.stringPiece());
// Make sure no socket already exists at this path
int rc = unlink(thriftSocketPath.value().c_str());
if (rc != 0 && errno != ENOENT) {
int errnum = errno;
startupLogger->exitUnsuccessfully(
1,
"failed to remove eden socket at ",
thriftSocketPath,
": ",
folly::errnoStr(errnum));
}
FakeEdenServer server;
server.setCommandLine(originalCommandArguments);
if (!FLAGS_cleanShutdownFile.empty()) {
server.setCleanShutdownFile(AbsolutePath{FLAGS_cleanShutdownFile});
}
if (FLAGS_ignoreStop) {
server.setStopBehavior(StopBehavior::DoNothing);
}
if (FLAGS_exitWithoutCleanupOnStop) {
server.setStopBehavior(StopBehavior::ExitWithoutCleanup);
}
server.setGetPidSleepDuration(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::duration<double>{FLAGS_sleepBeforeGetPid}));
server.setStopSleepDuration(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::duration<double>{FLAGS_sleepBeforeStop}));
server.run(thriftAddress, *startupLogger);
return 0;
}
namespace {
template <class Rep, class Ratio>
std::string prettyPrint(
std::chrono::duration<Rep, Ratio> duration,
bool addSpace) {
auto durationInSeconds =
std::chrono::duration_cast<std::chrono::duration<double>>(duration);
return folly::prettyPrint(
durationInSeconds.count(), folly::PRETTY_TIME, addSpace);
}
} // namespace