mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 06:18:07 +03:00
Run Eden as a Windows service
Summary: By default the Eden on Windows will run as a service. It should be installed as a Windows service by the installer to work properly. Reviewed By: wez Differential Revision: D21241597 fbshipit-source-id: 2bcbd518d274d829bee5616d266c542f3fcc4b16
This commit is contained in:
parent
b3e00d0fe3
commit
f1fb15677b
@ -7,6 +7,7 @@ if(WIN32)
|
||||
add_executable(
|
||||
edenfs
|
||||
win/service/main.cpp
|
||||
win/service/WinService.cpp
|
||||
)
|
||||
else()
|
||||
add_executable(
|
||||
|
270
eden/fs/win/service/WinService.cpp
Normal file
270
eden/fs/win/service/WinService.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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 "WinService.h"
|
||||
#include <winerror.h>
|
||||
|
||||
#include <folly/Conv.h>
|
||||
#include <folly/experimental/FunctionScheduler.h>
|
||||
#include <folly/init/Init.h>
|
||||
#include <folly/logging/Init.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <thrift/lib/cpp2/server/ThriftServer.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include "eden/fs/model/Hash.h"
|
||||
#include "eden/fs/model/Tree.h"
|
||||
#include "eden/fs/service/EdenInit.h"
|
||||
#include "eden/fs/service/EdenServer.h"
|
||||
#include "eden/fs/service/StartupLogger.h"
|
||||
#include "eden/fs/telemetry/SessionInfo.h"
|
||||
#include "eden/fs/win/utils/StringConv.h"
|
||||
#include "eden/fs/win/utils/WinError.h"
|
||||
#include "folly/io/IOBuf.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace folly;
|
||||
|
||||
// TODO(puneetk): Logging on Windows doesn't work when the async is set. Fix the
|
||||
// Async logging and enable the default logging below.
|
||||
|
||||
// Set the default log level for all eden logs to DBG2
|
||||
// Also change the "default" log handler (which logs to stderr) to log
|
||||
// messages asynchronously rather than blocking in the logging thread.
|
||||
// FOLLY_INIT_LOGGING_CONFIG("eden=DBG2; default:async=true");
|
||||
FOLLY_INIT_LOGGING_CONFIG("eden=DBG4");
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
namespace {
|
||||
|
||||
#define NO_ERROR 0
|
||||
#define SVCNAME L"Edenfs"
|
||||
constexpr StringPiece kEdenVersion = "edenwin";
|
||||
|
||||
void debugSetLogLevel(std::string category, std::string level) {
|
||||
auto& db = folly::LoggerDB::get();
|
||||
db.getCategoryOrNull(category);
|
||||
folly::Logger(category).getCategory()->setLevel(
|
||||
folly::stringToLogLevel(level), true);
|
||||
}
|
||||
|
||||
void redirectLogOutput(const char* logPath) {
|
||||
HANDLE newHandle = CreateFileA(
|
||||
logPath,
|
||||
FILE_APPEND_DATA,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr,
|
||||
OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
|
||||
if (newHandle == INVALID_HANDLE_VALUE) {
|
||||
throw facebook::eden::makeWin32ErrorExplicit(
|
||||
GetLastError(),
|
||||
folly::sformat("Unable to open the log file {}\n", logPath));
|
||||
}
|
||||
|
||||
// Don't close the previous handles here, it will be closed as part of _dup2
|
||||
// call.
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, newHandle);
|
||||
SetStdHandle(STD_ERROR_HANDLE, newHandle);
|
||||
|
||||
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(newHandle), _O_APPEND);
|
||||
|
||||
if (fd == -1) {
|
||||
throw std::runtime_error(
|
||||
"_open_osfhandle() returned -1 while opening logfile");
|
||||
}
|
||||
|
||||
if (_dup2(fd, STDERR_FILENO) == -1) {
|
||||
throw std::runtime_error(
|
||||
folly::format("Dup failed to update stderr. errno: {}", errno).str());
|
||||
}
|
||||
|
||||
if (_dup2(fd, STDOUT_FILENO) == -1) {
|
||||
throw std::runtime_error(
|
||||
folly::format("Dup failed to update stdout. errno: {}", errno).str());
|
||||
}
|
||||
|
||||
SCOPE_EXIT {
|
||||
_close(fd);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We create WinService as a global variable, so we could use it in the static
|
||||
* functions callbacks from Windows SCM and our C++ class.
|
||||
*/
|
||||
WinService service;
|
||||
|
||||
const SERVICE_TABLE_ENTRY dispatchTable[] = {
|
||||
{(LPWSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION)WinService::main},
|
||||
{nullptr, nullptr}};
|
||||
} // namespace
|
||||
void WINAPI WinService::main(DWORD argc, LPSTR* argv) {
|
||||
service.serviceMain(argc, argv);
|
||||
}
|
||||
|
||||
void WinService::create(int argc, char** argv) {
|
||||
auto identity = UserInfo::lookup();
|
||||
auto userHome = identity.getHomeDirectory().stringPiece().toString();
|
||||
std::string dotEden = userHome + "\\.eden";
|
||||
std::string logFile = dotEden + "\\edenstartup.log";
|
||||
|
||||
if (!CreateDirectoryA(dotEden.c_str(), nullptr)) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != ERROR_ALREADY_EXISTS) {
|
||||
// If it fails to create the .eden directory for some reason - it won't
|
||||
// fail here but changes the startup log path.
|
||||
logFile = userHome + "\\edenstartup.log";
|
||||
}
|
||||
}
|
||||
redirectLogOutput(logFile.c_str());
|
||||
XLOG(INFO) << "Starting Eden Service";
|
||||
|
||||
// This call returns when the service has stopped.
|
||||
// The process should simply terminate when the call returns.
|
||||
if (!StartServiceCtrlDispatcher(dispatchTable)) {
|
||||
XLOG(ERR) << "Failed :" << GetLastError();
|
||||
}
|
||||
XLOG(INFO) << "Service Exited" << GetLastError();
|
||||
}
|
||||
|
||||
int WinService::serviceMain(int argc, char** argv) {
|
||||
handle_ = RegisterServiceCtrlHandler(SVCNAME, ctrlHandler);
|
||||
if (!handle_) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"RegisterServiceCtrlHandler failed. error %d \n",
|
||||
GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
status_.dwServiceType = SERVICE_USER_OWN_PROCESS;
|
||||
status_.dwServiceSpecificExitCode = 0;
|
||||
|
||||
// Setting a 3000 millisecond estimated wait for the the start pending. We
|
||||
// should not need more than this. If this starts to timeout we could increase
|
||||
// the wait hint.
|
||||
reportStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
|
||||
setup(argc, argv);
|
||||
reportStatus(SERVICE_RUNNING, NO_ERROR, 0);
|
||||
run();
|
||||
reportStatus(SERVICE_STOPPED, NO_ERROR, 0);
|
||||
|
||||
XLOG(INFO) << "Eden Windows - exiting";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int WinService::setup(int argc, char** argv) {
|
||||
auto identity = UserInfo::lookup();
|
||||
auto privHelper = make_unique<PrivHelper>();
|
||||
std::vector<std::string> originalCommandLine{argv, argv + argc};
|
||||
|
||||
std::unique_ptr<EdenConfig> edenConfig;
|
||||
try {
|
||||
edenConfig = getEdenConfig(identity);
|
||||
} catch (const ArgumentError& ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
auto logPath = getLogPath(edenConfig->edenDir.getValue());
|
||||
if (!logPath.empty()) {
|
||||
redirectLogOutput(logPath.c_str());
|
||||
}
|
||||
} catch (std::exception& ex) {
|
||||
// If the log redirection fails this error will show up on stderr. When Eden
|
||||
// is running in the background, this error will be lost. If the log file is
|
||||
// empty we should run the edenfs.exe on the console to get the error.
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set some default glog settings, to be applied unless overridden on the
|
||||
// command line
|
||||
gflags::SetCommandLineOptionWithMode(
|
||||
"logtostderr", "1", gflags::SET_FLAGS_DEFAULT);
|
||||
gflags::SetCommandLineOptionWithMode(
|
||||
"minloglevel", "0", gflags::SET_FLAGS_DEFAULT);
|
||||
|
||||
auto prepareFuture = folly::Future<folly::Unit>::makeEmpty();
|
||||
auto startupLogger = std::make_shared<ForegroundStartupLogger>();
|
||||
|
||||
SessionInfo sessionInfo;
|
||||
sessionInfo.username = identity.getUsername();
|
||||
sessionInfo.hostname = getHostname();
|
||||
sessionInfo.os = getOperatingSystemName();
|
||||
sessionInfo.osVersion = getOperatingSystemVersion();
|
||||
sessionInfo.edenVersion = kEdenVersion.str();
|
||||
|
||||
try {
|
||||
server_.emplace(
|
||||
std::move(originalCommandLine),
|
||||
std::move(identity),
|
||||
std::move(sessionInfo),
|
||||
std::move(privHelper),
|
||||
std::move(edenConfig));
|
||||
prepareFuture = server_->prepare(startupLogger);
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "Error: failed to start Eden: %s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
void WinService::run() {
|
||||
server_->getServer()->serve();
|
||||
server_->performCleanup();
|
||||
}
|
||||
|
||||
VOID WinService::reportStatus(
|
||||
DWORD currentState,
|
||||
DWORD exitCode,
|
||||
DWORD waitHint) {
|
||||
status_.dwCurrentState = currentState;
|
||||
status_.dwWin32ExitCode = exitCode;
|
||||
status_.dwWaitHint = waitHint;
|
||||
|
||||
if (currentState == SERVICE_START_PENDING)
|
||||
status_.dwControlsAccepted = 0;
|
||||
else
|
||||
status_.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
|
||||
if ((currentState == SERVICE_RUNNING) || (currentState == SERVICE_STOPPED))
|
||||
status_.dwCheckPoint = 0;
|
||||
else
|
||||
status_.dwCheckPoint = dwCheckPoint_++;
|
||||
|
||||
// Report the status of the service to the SCM.
|
||||
SetServiceStatus(handle_, &status_);
|
||||
}
|
||||
|
||||
void WinService::stop() {
|
||||
if (server_.has_value()) {
|
||||
server_.value().stop();
|
||||
}
|
||||
}
|
||||
|
||||
void WINAPI WinService::ctrlHandler(DWORD dwCtrl) {
|
||||
switch (dwCtrl) {
|
||||
case SERVICE_CONTROL_STOP:
|
||||
service.reportStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
|
||||
service.stop();
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace eden
|
||||
} // namespace facebook
|
58
eden/fs/win/service/WinService.h
Normal file
58
eden/fs/win/service/WinService.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <folly/portability/Windows.h>
|
||||
|
||||
#include <optional>
|
||||
#include "eden/fs/service/EdenServer.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace eden {
|
||||
|
||||
class WinService {
|
||||
public:
|
||||
WinService() = default;
|
||||
~WinService() = default;
|
||||
|
||||
WinService(const WinService&) = delete;
|
||||
WinService(WinService&&) = delete;
|
||||
|
||||
WinService& operator=(const WinService&) = delete;
|
||||
WinService& operator=(WinService&&) = delete;
|
||||
|
||||
/**
|
||||
* This function will create the dispatch table and launch the Edenfs service.
|
||||
* It will only return either on error or when the service exits. The error
|
||||
* log if the service failed to start will be in the edenstartup.log
|
||||
*/
|
||||
static void create(int argc, char** argv);
|
||||
|
||||
/**
|
||||
* This is the main function for the Edenfs service.
|
||||
*/
|
||||
static void WINAPI main(DWORD argc, LPSTR* argv);
|
||||
|
||||
private:
|
||||
int serviceMain(int argc, char** argv);
|
||||
|
||||
int setup(int argc, char** argv);
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
static void WINAPI ctrlHandler(DWORD dwCtrl);
|
||||
void reportStatus(DWORD currentState, DWORD exitCode, DWORD waitHint);
|
||||
|
||||
SERVICE_STATUS status_;
|
||||
SERVICE_STATUS_HANDLE handle_;
|
||||
|
||||
std::optional<EdenServer> server_;
|
||||
DWORD dwCheckPoint_{1};
|
||||
};
|
||||
|
||||
} // namespace eden
|
||||
} // namespace facebook
|
@ -21,6 +21,7 @@
|
||||
#include "eden/fs/service/EdenServer.h"
|
||||
#include "eden/fs/service/StartupLogger.h"
|
||||
#include "eden/fs/telemetry/SessionInfo.h"
|
||||
#include "eden/fs/win/service/WinService.h"
|
||||
#include "eden/fs/win/utils/StringConv.h"
|
||||
#include "folly/io/IOBuf.h"
|
||||
#include "folly/portability/Windows.h"
|
||||
@ -33,16 +34,6 @@ using namespace facebook::eden;
|
||||
using namespace std;
|
||||
using namespace folly;
|
||||
|
||||
// TODO(puneetk): Logging on Windows doesn't work when the async is set. Fix the
|
||||
// Async logging and enable the default logging below.
|
||||
|
||||
// Set the default log level for all eden logs to DBG2
|
||||
// Also change the "default" log handler (which logs to stderr) to log
|
||||
// messages asynchronously rather than blocking in the logging thread.
|
||||
// FOLLY_INIT_LOGGING_CONFIG("eden=DBG2; default:async=true");
|
||||
|
||||
FOLLY_INIT_LOGGING_CONFIG("eden=DBG4");
|
||||
|
||||
// The --edenfs flag is defined to help make the flags consistent across Windows
|
||||
// and non-Windows platforms. On non-Windows platform this flag is required, as
|
||||
// a check to help ensure that users do not accidentally invoke `edenfs` when
|
||||
@ -53,79 +44,7 @@ DEFINE_bool(
|
||||
false,
|
||||
"This optional argument is currently ignored on Windows");
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr StringPiece kEdenVersion = "edenwin";
|
||||
|
||||
} // namespace
|
||||
|
||||
int __cdecl main(int argc, char** argv) {
|
||||
XLOG(INFO) << "Eden Windows - starting";
|
||||
std::vector<std::string> originalCommandLine{argv, argv + argc};
|
||||
|
||||
// Make sure to run this before any flag values are read.
|
||||
folly::init(&argc, &argv);
|
||||
|
||||
auto identity = UserInfo::lookup();
|
||||
auto privHelper = make_unique<PrivHelper>();
|
||||
|
||||
std::unique_ptr<EdenConfig> edenConfig;
|
||||
try {
|
||||
edenConfig = getEdenConfig(identity);
|
||||
} catch (const ArgumentError& ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set some default glog settings, to be applied unless overridden on the
|
||||
// command line
|
||||
gflags::SetCommandLineOptionWithMode(
|
||||
"logtostderr", "1", gflags::SET_FLAGS_DEFAULT);
|
||||
gflags::SetCommandLineOptionWithMode(
|
||||
"minloglevel", "0", gflags::SET_FLAGS_DEFAULT);
|
||||
|
||||
auto prepareFuture = folly::Future<folly::Unit>::makeEmpty();
|
||||
std::shared_ptr<StartupLogger> startupLogger;
|
||||
try {
|
||||
auto logPath = getLogPath(edenConfig->edenDir.getValue());
|
||||
startupLogger = daemonizeIfRequested(logPath);
|
||||
} catch (std::exception& ex) {
|
||||
// If the log redirection fails this error will show up on stderr. When Eden
|
||||
// is running in the background, this error will be lost. If the log file is
|
||||
// empty we should run the edenfs.exe on the console to get the error.
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
SessionInfo sessionInfo;
|
||||
sessionInfo.username = identity.getUsername();
|
||||
sessionInfo.hostname = getHostname();
|
||||
sessionInfo.os = getOperatingSystemName();
|
||||
sessionInfo.osVersion = getOperatingSystemVersion();
|
||||
sessionInfo.edenVersion = kEdenVersion.str();
|
||||
|
||||
std::optional<EdenServer> server;
|
||||
try {
|
||||
server.emplace(
|
||||
std::move(originalCommandLine),
|
||||
std::move(identity),
|
||||
std::move(sessionInfo),
|
||||
std::move(privHelper),
|
||||
std::move(edenConfig));
|
||||
prepareFuture = server->prepare(startupLogger);
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "Error: failed to start EdenFS: %s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
server->getServer()->serve();
|
||||
server->performCleanup();
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "Error while running EdenFS: %s\n", ex.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
XLOG(INFO) << "Eden Windows - exiting";
|
||||
return 0;
|
||||
};
|
||||
WinService::create(argc, argv);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user